#pragma once

#include "../../jsonxx/fifo_map.hpp"
#include "../../jsonxx/json.hpp"
#include "../commons/code_generator.hpp"
#include "my_lexer.hpp"
#include <filesystem>
#include <fstream>
#include <iostream>
#include <optional>

using nlohmann::fifo_map;
using namespace jsonxx;
namespace fs = std::filesystem;

using std::array;
using std::fstream;
using std::istream;
using std::optional;
using std::ostream;

template <class K, class V, class dummy_compare, class A>
using my_workaround_fifo_map = fifo_map<K, V, nlohmann::fifo_map_compare<K>, A>;

/**
 * Public APIs
 */

/**
 * Factory method to build the lexer from JSON file
 * @param iss input stream contains the JSON
 * @return lexer
 */
MyLexer buildLexerFromJson(istream &iss);

/**
 * Write the lexer to target path
 * @param lexer the lexer
 * @param pathname output path
 */ 
void writeLexer(const MyLexer &lexer, const string &pathname, string sourcepath);


MyLexer buildLexerFromJson(istream &iss) {
    cout << "[Info] Start building lexer, wait patiently...\n";
    MyLexer lexer;
    basic_json<my_workaround_fifo_map> jin;
    iss >> jin;
    clock_t st = clock();
    unsigned int priority = 0;
    for (auto const &[name, regJ] : jin.as_object()) {
        lexer.addRegex(name, regJ.as_string(), priority);
        priority++;
    }
    clock_t ed = clock();
    cout << "[Info] Lexer built in " << ed - st << " ms\n";
    return lexer;
}


static void toInitList(ostream &oss, const optional<char> &obj) {
    oss << (int)obj.value_or(0) ; //TODO stupid
}


static void writeLexerHeader(ostream& oss){
    oss << "/**\n * Lexer generated by MyLexer lexer generator V1.0.0\n"
           " * Created at " << __DATE__ << " " << __TIME__ "\n"
           " * Built by Zeege (R) with ♥\n"
           " * See https://gitee.com/zeege/my-lex for details\n**/\n\n";
    oss << "#pragma once\n\n";
    oss << "#include \"lexer_typedef_min.hpp\"\n\n";
    oss << "MyLexer buildLexer();\n\n";
}

static void writeLexerDemo(ostream& oss){
    oss << "/**\n * This is a demo for using the lexer\n * Happy lexing!\n */\n";
    oss << "int main() {\n";
    oss << "\tclock_t st = clock();\n";
    oss << "\tconst MyLexer lexer = buildLexer();\n";
    oss << "\tclock_t ed = clock();\n";
    oss << "\tcout << \"Lexer built in \" << ed - st << \" ms\\n\";\n";
    oss << "\tcin >> std::noskipws;\n";
    oss << "\tstring s;\n"
           "\twhile (true) {\n"
           "\t\tcout << \"Input the string to identify: \";\n"
           "\t\tgetline(cin, s);\n"
           "\t\tvector<Token> tokens;\n"
           "\t\ttry { tokens = lexer.lex(s); }\n"
           "\t\tcatch (ParseException e) { continue; }\n"
           "\t\tfor (auto i : tokens) {\n"
           "\t\t\tcout << i.tokenName << \": \" << i.word << endl;\n\t\t}\n\t}\n";
    oss << "\treturn 0;\n}\n\n";
}

static void writeShared(ostream &oss, const vector<acceptableChars> &v,
                 const std::vector<std::vector<std::set<int>>> &states) {
    oss << "MyLexer buildLexer() {\n";
    oss << "\tconst vector<acceptableChars> symbols = ";
    toInitList(oss, v);
    oss << ";\n";
    oss << "\tconst vector<vector<set<int>>> states = ";
    toInitList(oss, states);
    oss << ";\n";
    oss << "\tFsaSharedField shared(symbols, states);\n";
}

static void writeLexerInternal(ostream &oss, const map<int, const string> &mp, const int beginState) {
    oss << "\tconst map<int, const string> acStates = ";
    toInitList(oss, mp);
    oss << ";\n";
    oss << "\treturn MyLexer(" << beginState << ", " << "shared, acStates);\n}";
}

/**
 * Write matrix-based lexer to target path
 * @param lexer the lexer
 * @param pathname path to write
 */
void writeLexer(const MyLexer &lexer, const string& pathname, string sourcepath = "./", bool writeMain = true) {
    cout << "[Info] Start generating lexer, wait patiently...\n";
    fs::path pp(pathname);
    if(!fs::exists(pp)){
        fs::create_directory(pp);
    }
    fs::remove(pathname + "/lexer_typedef_min.hpp");
    fs::remove(pathname + "/lexer.cpp");
    fs::copy_file(sourcepath +"/lexer_typedef_min.hpp",
                  pathname + "/lexer_typedef_min.hpp");
    fstream fss;
    __OPEN_AND_CHECK(fss, pathname + "/lexer.cpp", true);
    writeLexerHeader(fss);
    if(writeMain){
        writeLexerDemo(fss);
    }
    writeShared(fss, lexer.getSharedField().getSymbols(),
                lexer.getSharedField().getStates());
    writeLexerInternal(fss, lexer.getDFABase().getAcStates(), lexer.getDFABase().getBeginState());
    fss.close();
    cout << "[Info] Lexer created at " << pathname << "/lexer.cpp\n\n";
}
